#include "DetectionBasedTracker_jni.h"
#include <opencv2/core.hpp>
#include <opencv2/objdetect.hpp>

#include <string>
#include <vector>

#include <android/log.h>
#include <opencv2/opencv.hpp>
#include <opencv2/face/facerec.hpp>

#define LOG_TAG "FaceDetection/DetectionBasedTracker"
#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))

#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))

using namespace std;
using namespace cv;

inline void vector_Rect_to_Mat(vector<Rect> &v_rect, Mat &mat) {
    mat = Mat(v_rect, true);
}

class CascadeDetectorAdapter : public DetectionBasedTracker::IDetector {
public:
    CascadeDetectorAdapter(cv::Ptr<cv::CascadeClassifier> detector) :
            IDetector(),
            Detector(detector) {
        LOGD("CascadeDetectorAdapter::Detect::Detect");
        CV_Assert(detector);
    }

    void detect(const cv::Mat &Image, std::vector<cv::Rect> &objects) {
        LOGD("CascadeDetectorAdapter::Detect: begin");
        LOGD("CascadeDetectorAdapter::Detect: scaleFactor=%.2f, minNeighbours=%d, minObjSize=(%dx%d), maxObjSize=(%dx%d)",
             scaleFactor, minNeighbours, minObjSize.width, minObjSize.height, maxObjSize.width,
             maxObjSize.height);
        Detector->detectMultiScale(Image, objects, scaleFactor, minNeighbours, 0, minObjSize,
                                   maxObjSize);
        LOGD("CascadeDetectorAdapter::Detect: end");
    }

    virtual ~CascadeDetectorAdapter() {
        LOGD("CascadeDetectorAdapter::Detect::~Detect");
    }

private:
    CascadeDetectorAdapter();

    cv::Ptr<cv::CascadeClassifier> Detector;
};

struct DetectorAgregator {
    cv::Ptr<CascadeDetectorAdapter> mainDetector;
    cv::Ptr<CascadeDetectorAdapter> trackingDetector;

    cv::Ptr<DetectionBasedTracker> tracker;

    DetectorAgregator(cv::Ptr<CascadeDetectorAdapter> &_mainDetector,
                      cv::Ptr<CascadeDetectorAdapter> &_trackingDetector) :
            mainDetector(_mainDetector),
            trackingDetector(_trackingDetector) {
        CV_Assert(_mainDetector);
        CV_Assert(_trackingDetector);

        DetectionBasedTracker::Parameters DetectorParams;
        tracker = makePtr<DetectionBasedTracker>(mainDetector, trackingDetector, DetectorParams);
    }
};

JNIEXPORT jlong JNICALL
Java_com_csizg_facemodel_tracker_DetectionBasedTracker_nativeCreateObject
        (JNIEnv *jenv, jclass, jstring jFileName, jint faceSize) {
    LOGD("Java_com_csizg_facemodel_tracker_DetectionBasedTracker_nativeCreateObject enter");
    const char *jnamestr = jenv->GetStringUTFChars(jFileName, NULL);
    string stdFileName(jnamestr);
    jlong result = 0;

    LOGD("Java_com_csizg_facemodel_tracker_DetectionBasedTracker_nativeCreateObject");

    try {
        cv::Ptr<CascadeDetectorAdapter> mainDetector = makePtr<CascadeDetectorAdapter>(
                makePtr<CascadeClassifier>(stdFileName));
        cv::Ptr<CascadeDetectorAdapter> trackingDetector = makePtr<CascadeDetectorAdapter>(
                makePtr<CascadeClassifier>(stdFileName));
        result = (jlong) new DetectorAgregator(mainDetector, trackingDetector);
        if (faceSize > 0) {
            mainDetector->setMinObjectSize(Size(faceSize, faceSize));
            //trackingDetector->setMinObjectSize(Size(faceSize, faceSize));
        }
    }
    catch (cv::Exception &e) {
        LOGD("nativeCreateObject caught cv::Exception: %s", e.what());
        jclass je = jenv->FindClass("org/opencv/core/CvException");
        if (!je)
            je = jenv->FindClass("java/lang/Exception");
        jenv->ThrowNew(je, e.what());
    }
    catch (...) {
        LOGD("nativeCreateObject caught unknown exception");
        jclass je = jenv->FindClass("java/lang/Exception");
        jenv->ThrowNew(je,
                       "Unknown exception in JNI code of DetectionBasedTracker.nativeCreateObject()");
        return 0;
    }

    LOGD("Java_com_csizg_facemodel_tracker_DetectionBasedTracker_nativeCreateObject exit");
    return result;
}

JNIEXPORT void JNICALL
Java_com_csizg_facemodel_tracker_DetectionBasedTracker_nativeDestroyObject
        (JNIEnv *jenv, jclass, jlong thiz) {
    LOGD("Java_com_csizg_facemodel_tracker_DetectionBasedTracker_nativeDestroyObject");

    try {
        if (thiz != 0) {
            ((DetectorAgregator *) thiz)->tracker->stop();
            delete (DetectorAgregator *) thiz;
        }
    }
    catch (cv::Exception &e) {
        LOGD("nativeestroyObject caught cv::Exception: %s", e.what());
        jclass je = jenv->FindClass("org/opencv/core/CvException");
        if (!je)
            je = jenv->FindClass("java/lang/Exception");
        jenv->ThrowNew(je, e.what());
    }
    catch (...) {
        LOGD("nativeDestroyObject caught unknown exception");
        jclass je = jenv->FindClass("java/lang/Exception");
        jenv->ThrowNew(je,
                       "Unknown exception in JNI code of DetectionBasedTracker.nativeDestroyObject()");
    }
    LOGD("Java_com_csizg_facemodel_tracker_DetectionBasedTracker_nativeDestroyObject exit");
}

JNIEXPORT void JNICALL Java_com_csizg_facemodel_tracker_DetectionBasedTracker_nativeStart
        (JNIEnv *jenv, jclass, jlong thiz) {
    LOGD("Java_com_csizg_facemodel_tracker_DetectionBasedTracker_nativeStart");

    try {
        ((DetectorAgregator *) thiz)->tracker->run();
    }
    catch (cv::Exception &e) {
        LOGD("nativeStart caught cv::Exception: %s", e.what());
        jclass je = jenv->FindClass("org/opencv/core/CvException");
        if (!je)
            je = jenv->FindClass("java/lang/Exception");
        jenv->ThrowNew(je, e.what());
    }
    catch (...) {
        LOGD("nativeStart caught unknown exception");
        jclass je = jenv->FindClass("java/lang/Exception");
        jenv->ThrowNew(je, "Unknown exception in JNI code of DetectionBasedTracker.nativeStart()");
    }
    LOGD("Java_com_csizg_facemodel_tracker_DetectionBasedTracker_nativeStart exit");
}

JNIEXPORT void JNICALL Java_com_csizg_facemodel_tracker_DetectionBasedTracker_nativeStop
        (JNIEnv *jenv, jclass, jlong thiz) {
    LOGD("Java_com_csizg_facemodel_tracker_DetectionBasedTracker_nativeStop");

    try {
        ((DetectorAgregator *) thiz)->tracker->stop();
    }
    catch (cv::Exception &e) {
        LOGD("nativeStop caught cv::Exception: %s", e.what());
        jclass je = jenv->FindClass("org/opencv/core/CvException");
        if (!je)
            je = jenv->FindClass("java/lang/Exception");
        jenv->ThrowNew(je, e.what());
    }
    catch (...) {
        LOGD("nativeStop caught unknown exception");
        jclass je = jenv->FindClass("java/lang/Exception");
        jenv->ThrowNew(je, "Unknown exception in JNI code of DetectionBasedTracker.nativeStop()");
    }
    LOGD("Java_com_csizg_facemodel_tracker_DetectionBasedTracker_nativeStop exit");
}

JNIEXPORT void JNICALL
Java_com_csizg_facemodel_tracker_DetectionBasedTracker_nativeSetFaceSize
        (JNIEnv *jenv, jclass, jlong thiz, jint faceSize) {
    LOGD("Java_com_csizg_facemodel_tracker_DetectionBasedTracker_nativeSetFaceSize -- BEGIN");

    try {
        if (faceSize > 0) {
            ((DetectorAgregator *) thiz)->mainDetector->setMinObjectSize(Size(faceSize, faceSize));
            //((DetectorAgregator*)thiz)->trackingDetector->setMinObjectSize(Size(faceSize, faceSize));
        }
    }
    catch (cv::Exception &e) {
        LOGD("nativeStop caught cv::Exception: %s", e.what());
        jclass je = jenv->FindClass("org/opencv/core/CvException");
        if (!je)
            je = jenv->FindClass("java/lang/Exception");
        jenv->ThrowNew(je, e.what());
    }
    catch (...) {
        LOGD("nativeSetFaceSize caught unknown exception");
        jclass je = jenv->FindClass("java/lang/Exception");
        jenv->ThrowNew(je,
                       "Unknown exception in JNI code of DetectionBasedTracker.nativeSetFaceSize()");
    }
    LOGD("Java_com_csizg_facemodel_tracker_DetectionBasedTracker_nativeSetFaceSize -- END");
}


JNIEXPORT void JNICALL Java_com_csizg_facemodel_tracker_DetectionBasedTracker_nativeDetect
        (JNIEnv *jenv, jclass, jlong thiz, jlong imageGray, jlong faces) {
    LOGD("Java_com_csizg_facemodel_tracker_DetectionBasedTracker_nativeDetect");

    try {
        vector<Rect> RectFaces;
        ((DetectorAgregator *) thiz)->tracker->process(*((Mat *) imageGray));
        ((DetectorAgregator *) thiz)->tracker->getObjects(RectFaces);
        *((Mat *) faces) = Mat(RectFaces, true);
    }
    catch (cv::Exception &e) {
        LOGD("nativeCreateObject caught cv::Exception: %s", e.what());
        jclass je = jenv->FindClass("org/opencv/core/CvException");
        if (!je)
            je = jenv->FindClass("java/lang/Exception");
        jenv->ThrowNew(je, e.what());
    }
    catch (...) {
        LOGD("nativeDetect caught unknown exception");
        jclass je = jenv->FindClass("java/lang/Exception");
        jenv->ThrowNew(je, "Unknown exception in JNI code DetectionBasedTracker.nativeDetect()");
    }
    LOGD("Java_com_csizg_facemodel_tracker_DetectionBasedTracker_nativeDetect END");
}
//
//void
//read_csv(const string &filename, vector<Mat> &images, vector<int> &labels, char separator = ';') {
//    ifstream file(filename.c_str(), ifstream::in);
//    if (!file) {
//        return;
//    }
//
//    String line, path, classlabel;
//    while (getline(file, line)) {
//        stringstream liness(line);
//        getline(liness, path, separator);
//        getline(liness, classlabel);
//        if (!path.empty() && !classlabel.empty()) {
//            images.push_back(imread(path, 0));
//            labels.push_back(atoi(classlabel.c_str()));
//        }
//    }
//}
//
//vector<Mat> images;
//vector<int> labels;
//
//Ptr<face::LBPHFaceRecognizer> globalLBPHModel;
//
////读取csv配置，开始训练
//JNIEXPORT void JNICALL
//Java_com_csizg_facemodel_tracker_DetectionBasedTracker_nativeSaveLBPHFaceRecognizer(
//        JNIEnv *jenv, jclass, jstring fn_csv, jstring model_xml) {
//    //读取你的CSV文件路径
//    //读取数据。如果文件不合法就会出错
//    //输入的文件名已经有了
//
//    ///mnt/sdcard/at.txt
//    const char *jcsvstr = jenv->GetStringUTFChars(fn_csv, NULL);
//    string stdFileName(jcsvstr);
//
//    try {
//        images.clear();
//        labels.clear();
//        read_csv(jcsvstr, images, labels, ';');
//    } catch (Exception &e) {
//        LOGE("Error opening file %s  . Reason:%s", jcsvstr, e.msg.c_str());
//        return;
//    }
//
//    Ptr<face::LBPHFaceRecognizer> model = face::LBPHFaceRecognizer::create();
//    LOGE("开始训练");
//    model->train(images, labels);
//    LOGE("结束训练");
//
//    const char *jcsvstr1 = jenv->GetStringUTFChars(model_xml, NULL);
//    string modelXmlName(jcsvstr1);
//
//    LOGE("开始保存训练集");
//    model->save(modelXmlName);
//    LOGE("结束保存训练集");
//    globalLBPHModel = model;
//    return;
//}
//
////加载训练数据
//JNIEXPORT void JNICALL
//Java_com_csizg_facemodel_tracker_DetectionBasedTracker_nativeLoadLBPHFaceRecognizer(
//        JNIEnv *jenv, jclass, jstring model_xml) {
//    const char *jcsvstr = jenv->GetStringUTFChars(model_xml, NULL);
//    string modelXmlName(jcsvstr);
//
//    fstream _file;
//    //"/mnt/sdcard/MyFaceLBPHModel.xml"
//    _file.open(modelXmlName, ios::in);
//    if (!_file) {
//        LOGE("没有训练数据");
//    } else {
//        globalLBPHModel = Algorithm::load<face::LBPHFaceRecognizer>(modelXmlName);
//    }
//}
//
////识别人
//JNIEXPORT jstring JNICALL
//Java_com_csizg_facemodel_tracker_DetectionBasedTracker_nativeRecognizer(
//        JNIEnv *env, jclass, jlong inputImage) {
//    imwrite("/mnt/sdcard/lbph.jpg", *((Mat *) inputImage));
//    if (globalLBPHModel == NULL) {
//        return env->NewStringUTF("");
//    }
//    int predictedLabel;
//    try {
//        predictedLabel = globalLBPHModel->predict(*((Mat *) inputImage));
//    } catch (Exception &e) {
//        LOGE("lbph识别失败");
//        predictedLabel = -1;
//    }
//
//    string result_message = format("Predictrd name = %d", predictedLabel);
//    return env->NewStringUTF(result_message.c_str());
//}
//
//
////保存数据
//JNIEXPORT jstring JNICALL
//Java_com_csizg_facemodel_tracker_DetectionBasedTracker_nativeSaveImage(
//        JNIEnv *jenv, jclass, jlong inputImage, jstring image_path) {
//
//    imwrite("/mnt/sdcard/lbph.jpg", *((Mat *) inputImage));
//    if (globalLBPHModel == NULL) {
//        return jenv->NewStringUTF("");
//    }
//    int predictedLabel;
//    try {
//        predictedLabel = globalLBPHModel->predict(*((Mat *) inputImage));
//    } catch (Exception &e) {
//        LOGE("lbph识别失败");
//        predictedLabel = -1;
//    }
//
//    const char *jcsvstr = jenv->GetStringUTFChars(image_path, NULL);
//    string imagePathName(jcsvstr);
//    string resultPath = format("%s/s%d", imagePathName, predictedLabel);
//    imwrite(resultPath, *((Mat *) inputImage));
//    string result_message = format("%s/s%d", imagePathName, predictedLabel);
//
//    return jenv->NewStringUTF(result_message.c_str());
//}

